/*-----------------------------------------------------------------------------------------------*/
/**
  @file main.c 
  @brief Example how to the Linux uevent mechanism 
*/
/*-----------------------------------------------------------------------------------------------*/

/*-------------------------------------------------------------------------------------------------
  Copyright (c) 2018 Quectel Wireless Solution, Co., Ltd. All Rights Reserved.
  Quectel Wireless Solution Proprietary and Confidential.
-------------------------------------------------------------------------------------------------*/

/*-------------------------------------------------------------------------------------------------
  EDIT HISTORY
  This section contains comments describing changes made to the file.
  Notice that changes are listed in reverse chronological order.
  $Header: $
  when       who          what, where, why
  --------   ---          ----------------------------------------------------------
  20190708   tyler.kuang  Created .
-------------------------------------------------------------------------------------------------*/



#include <string.h>
#include <unistd.h>
#include <time.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <linux/netlink.h>
#include <stdarg.h>
#include <ctype.h>

#define RECV_BUF_LEN  (16*1024)

typedef struct
{
    /** range: [add|change|remove] */
    const char *action;
    /** format : /devices/virtual/net/ecm0 */
    const char *devpath;
    const char *subsystem;
    /** interface name */
    const char *interface;
    /** range: [CONNECTED|CONNECTED|DISCONNECTED] */
    const char *usb_state;
} uevent_info_t;


static int g_fd_uevent = -1;


/*-----------------------------------------------------------------------------------------------*/
/** 
  @brief Handling uevent events
  @param[in] seqnum 
  @param[in] p_info uevent info
  */
/*-----------------------------------------------------------------------------------------------*/
void uevent_handler_cb(int seqnum, uevent_info_t *p_info)
{
    printf("seqnum    : %d\n", seqnum);
    printf("action    : %s\n", p_info->action);
    printf("devpath   : %s\n", p_info->devpath);
    printf("subsystem : %s\n", p_info->subsystem);
    printf("interface : %s\n", p_info->interface);
    printf("usb_state : %s\n", p_info->usb_state);

    printf("\n\n");
}

int uevent_init(void)
{
    struct sockaddr_nl addr;
    int sz = 64*1024;
    int fd;

    printf("Enter %s\n", __FUNCTION__);

    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid();
    addr.nl_groups = 0xffffffff;

    fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
    if (fd < 0) {
        printf("Failed to open netlink, err=%s\n", strerror(errno));
        return -1;
    }

    setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));

    if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
        printf("Failed to bind, err=%s\n", strerror(errno));
        close(fd);
        return 0;
    }

    g_fd_uevent = fd;

    printf("Exit %s\n", __FUNCTION__);

    return 0;
}

void uevent_info_init(uevent_info_t *p_info)
{
    memset(p_info, 0, sizeof(*p_info));
    p_info->action = "unknow";
    p_info->devpath  = "unknow";
    p_info->subsystem = "unknow";
    p_info->interface = "unknow";
    p_info->usb_state = "unknow";
}

int uevent_loop(void)
{
    int ret;
    int len;
    char *p_buf = NULL;
    fd_set rfds;
    int fd_max;
    struct timeval tv;
    uevent_info_t info;
    char *ptr_start, *ptr_end;
    char *ptr_tmp, *ptr_save;
    char *name, *value;
    int seqnum = 0;

    printf("Enter %s\n", __FUNCTION__);

    p_buf = malloc(RECV_BUF_LEN);

    if(p_buf == NULL)
    {
        printf("Memory is not enough\n");
        return -1;
    }

    while(1)
    {
        tv.tv_sec = 10;
        tv.tv_usec = 0;

        FD_ZERO(&rfds);
        FD_SET(g_fd_uevent, &rfds);

        fd_max = g_fd_uevent;

        ret = select(fd_max+1, &rfds, NULL, NULL, &tv);

        if(ret < 0)
        {
            if(errno == EINTR)    
            {
                continue;
            }

            break;
        }
        else if(ret == 0)
        {
            continue;
        }

        if(FD_ISSET(g_fd_uevent, &rfds))
        {
            len = recv(g_fd_uevent, p_buf, RECV_BUF_LEN, 0); 
            if(len <= 0)
            {
                if(errno == EINTR)    
                {
                    continue;
                }

                printf("Failed to recv, err=%s\n", strerror(errno));
                break;
            }
            
            ptr_start = p_buf;
            ptr_end = p_buf + len;

            uevent_info_init(&info);

            while(ptr_start < ptr_end)
            {
                ptr_tmp = ptr_start;
                ptr_start += strlen(ptr_start) + 1;        
                name = strtok_r(ptr_tmp, "=\r\n ", &ptr_save);
                if(name == NULL)
                {
                    continue;
                }

                value = strtok_r(NULL, "=\r\n ", &ptr_save);
                if(value == NULL)
                {
                    continue;
                }

                if(!strcasecmp(name, "ACTION"))
                {
                    info.action = value;
                }
                else if(!strcasecmp(name, "DEVPATH"))
                {
                    info.devpath = value;
                }
                else if(!strcasecmp(name, "SUBSYSTEM"))
                {
                    info.subsystem = value;
                }
                else if(!strcasecmp(name, "INTERFACE"))
                {
                    info.interface = value;
                }
                else if(!strcasecmp(name, "USB_STATE"))
                {
                    info.usb_state = value;
                }
                else if(!strcasecmp(name, "SEQNUM"))
                {
                    seqnum = atoi(value);
                    if(info.action != NULL) 
                    {
                        uevent_handler_cb(seqnum, &info);
                    }
                    uevent_info_init(&info);
                }
            }
        }
    }

    if(p_buf == NULL)
    {
        free(p_buf);
    }

    return 0;
}

int main(int argc, char **argv)
{
    printf("App start\n");

    uevent_init();
    uevent_loop();

    printf("App exit\n");
    return 0;
}
